﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Data;

namespace MF.Data.Entity
{
    public class EntityRepository<TEntity> : IRepository<TEntity>
        where TEntity : EntityObject, new()
    {
        public EntityRepository(IDbContextManager manager)
        {
            this.SetContext(manager.GetDbContext());
        }

        private void SetContext(IDbContext ctx)
        {
            this.ObjectContext = ctx.Context as ObjectContext;
            this.FetchEntitySetName();
        }

        protected ObjectContext ObjectContext { get; private set; }
        protected string EntitySetName { get; private set; }

        //looks for an IQueryable<TEntity> property in the ObjectContext 
        //and gets its name to be used in other methods
        private void FetchEntitySetName()
        {
            var entitySetProperty =
               this.ObjectContext.GetType().GetProperties()
                   .Single(p => p.PropertyType.IsGenericType && typeof(IQueryable<>)
                   .MakeGenericType(typeof(TEntity)).IsAssignableFrom(p.PropertyType));

            this.EntitySetName = entitySetProperty.Name;
        }

        //to be implemented by derived classes if needed
        protected virtual IQueryable<TEntity> BuildQuery(ObjectQuery<TEntity> query)
        {
            return query;
        }

        public virtual IQueryable<TEntity> Query()
        {
            var entitySet = String.Format("[{0}]", this.EntitySetName);
            var baseQuery = this.ObjectContext.CreateQuery<TEntity>(entitySet);
            return this.BuildQuery(baseQuery);
        }

        public virtual void Add(TEntity entity)
        {
            this.ObjectContext.AddObject(this.EntitySetName, entity);
        }

        public virtual void Update(TEntity entity)
        {
            if (entity.EntityKey == null)
                entity.EntityKey = this.ObjectContext.CreateEntityKey(this.EntitySetName, entity);

            object existingEntity = null;
            this.ObjectContext.TryGetObjectByKey(entity.EntityKey, out existingEntity);
            if (existingEntity == null)
            {
                throw new System.Data.UpdateException("The entity cannot be updated because it doesn't exist!");
            }
            this.ObjectContext.ApplyPropertyChanges(this.EntitySetName, entity);
        }

        public virtual void Delete(TEntity entity)
        {
            if (entity.EntityState == EntityState.Detached)
                this.ObjectContext.AttachTo(this.EntitySetName, entity);

            this.ObjectContext.DeleteObject(entity);
        }

        public void SaveChanges()
        {
            this.ObjectContext.SaveChanges();
        }
    }
}
